package com.ybear.ybutils.utils;

import android.app.Activity;
import android.app.ActivityManager;
import android.app.Application;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Process;
import android.text.TextUtils;

import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.arch.core.util.Function;

import com.ybear.ybutils.utils.handler.HandlerManage;
import com.ybear.ybutils.utils.toast.Build;
import com.ybear.ybutils.utils.toast.ToastManage;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 管理所有Activity和Service以及App的退出
 *
 * Activity：onCreate方法中调用{@link #addExitStack(Activity)}，
 *           onDestroy方法中调用{@link #removeExitStack(Activity)}
 *
 * Service：onCreate方法中调用{@link #addExitStack(Service)}，
 *           onDestroy方法中调用{@link #removeExitStack(Service)}
 *
 * @see #exitStack(Object, boolean) //私有方法
 * @see #addExitStack(Activity)
 * @see #addExitStack(Service)
 * @see #removeExitStack(Activity)
 * @see #removeExitStack(Service)
 * @see #exitApp(Context, int)
 * @see #exitApp(Context)
 * @see #exitApp(Context, long)
 * @see #exitActivityAll(Context)
 * @see #exitServiceAll(Context)
 * @see #exit(Object[])
 * @see #exitKeep(Object)
 * @see #isHaveExistActivity(Object[])
 * @see #isHaveExistActivityOfSkip(Object[])
 * @see #isHaveExistService(Object[])
 * @see #isHaveExistServiceOfSkip(Object[])
 * @see #isHaveExistAll(Object[])
 * @see #isHaveExistAllOfSkip(Object[])
 * @see #reStartApp(Context, Class)
 * @see #reStartApp(Context, Class, long)
 */
public class StackManage {
    /**
     * 退出类型
     */
    @IntDef({ ExitType.ALL, ExitType.ACTIVITY, ExitType.SERVICE })
    @Retention(RetentionPolicy.SOURCE)
    public @interface ExitType {
        int ALL = 0;
        int ACTIVITY = 1;
        int SERVICE = 2;
    }
    
    private static Map<String, Object> mExitAppMap;
    private static long firstTime = 0;
    private final com.ybear.ybutils.utils.handler.Handler mHandler;
    private String mExitHint, mPackagesName;

    private StackManage() {
        mHandler = HandlerManage.create();
    }

    public static StackManage get() { return HANDLER.INSTANCE; }
    private static final class HANDLER {
        private static final StackManage INSTANCE = new StackManage();
    }

    private boolean isZH() { return "zh".equals( SysUtil.getLanguage() ); }

    public StackManage init(Application app) {
        mPackagesName = AppUtil.getPackageName( app );
        return this;
    }

    /**
     * 设置退出
     * @param hint  提示内容
     * @return      this
     */
    public StackManage setDoubleBackPressedExitHint(String hint) {
        mExitHint = hint;
        return this;
    }

    /**
     * 设置包名
     * @param packagesName  包名
     * @return              this
     */
    public StackManage setPackagesName(String packagesName) {
        mPackagesName = packagesName;
        return this;
    }

    private String getPackagesName(@Nullable Context context) {
        if( context == null ) return mPackagesName;
        return TextUtils.isEmpty( mPackagesName ) ? AppUtil.getPackageName( context ) : mPackagesName;
    }

    /**
     * 退出栈
     * @param obj       退出的activity/service
     * @param isAdd     true：添加，false：移除
     */
    private void exitStack(@NonNull Object obj, boolean isAdd) {
        if( mExitAppMap == null ) { mExitAppMap = new HashMap<>(); }
        String key = getObjName( obj );
        boolean isExist = mExitAppMap.containsValue( obj );
        if( isAdd ) {
            if( !isExist ) mExitAppMap.put( key, obj );
        }else {
            if( isExist ) mExitAppMap.remove( key );
        }
    }

    /**
     * 在退出栈中添加当前Activity
     * 调用exitApp后会退出栈中的所有Activity
     * @param activity      {@link Activity}
     * @param <T>           泛型必须继承{@link Activity}
     */
    public <T extends Activity> void addExitStack(@NonNull T activity) {
        exitStack( activity, true );
    }

    /**
     * 在退出栈中添加当前Service
     * 调用exitApp后会退出栈中的所有Service
     * @param service       {@link Service}
     * @param <T>           泛型必须继承{@link Service}
     */
    public <T extends Service> void addExitStack(@NonNull T service) {
        exitStack( service, true );
    }

    /**
     * 在退出栈中移除当前Activity
     * @param activity      {@link Activity}
     * @param <T>           泛型必须继承{@link Activity}
     */
    public <T extends Activity> void removeExitStack(@NonNull T activity) {
        exitStack( activity, false );
    }

    /**
     * 在退出栈中移除当前Service
     * @param service       {@link Service}
     * @param <T>           泛型必须继承{@link Service}
     */
    public <T extends Service> void removeExitStack(@NonNull T service) {
        exitStack( service, false );
    }
    /**
     * 退出app
     * 退出会以栈的形式退出
     */
    public void exitApp(Context context, @ExitType int type) {
        boolean isAll = type == ExitType.ALL;
        boolean isAct = type == ExitType.ACTIVITY;
        boolean isSer = type == ExitType.SERVICE;
        if( mExitAppMap == null ) return;
        for( Object obj : mExitAppMap.values() ) {
            if( obj == null ) continue;
            if( ( isAll || isAct ) && obj instanceof Activity) ((Activity) obj).finish();
            if( ( isAll || isSer ) && obj instanceof Service) ((Service) obj).stopSelf();
        }
        if( isAll ) {
            //退出APP前发送通知
            DOM.getInstance().setResult( DOMConstant.DOM_EXIT_APP );
            //杀死自身
            SysUtil.killProcesses( context, getPackagesName( context ) );
            mHandler.post( () ->
                    System.exit( 0 ), 250
            );
        }
    }

    /**
     * 退出整个app
     */
    public void exitApp(Context context) { exitApp( context, ExitType.ALL ); }

    /**
     * 延迟退出app
     * @param delayMillis   延迟毫秒
     */
    public void exitApp(Context context, long delayMillis) {
        mHandler.post(() -> exitApp( context, ExitType.ALL ), delayMillis);
    }

    /**
     * 退出所有 Activity
     */
    public void exitActivityAll(Context context) { exitApp( context, ExitType.ACTIVITY ); }

    /**
     * 退出所有 Service
     */
    public void exitServiceAll(Context context) { exitApp( context, ExitType.SERVICE ); }

    /**
     * 退出指定的 Activity or Service
     * @param objArr   Activity or Service
     */
    public void exit(@Nullable Object... objArr) {
        if( objArr == null ) return;
        for( Object obj : objArr ) {
            if( obj == null ) continue;
            Object val = mExitAppMap.get( getObjName( obj ) );
            //当前Obj是否属于 Activity
            if( val instanceof Activity) ( (Activity) val ).finish();
            //当前Obj是否属于 Service
            if( val instanceof Service ) ( (Service) val ).stopSelf();
        }
    }

    /**
     * 退出除了指定Activity之外的 Activity or Service
     * @param obj       Activity or Service
     */
    public void exitKeep(@Nullable Object obj) {
        if( obj == null ) return;
        for( String key : mExitAppMap.keySet() ) {
            Object val = mExitAppMap.get( key );
            //跳过需要保持的 Activity or Service
            if( key.equals( getObjName( obj ) ) ) continue;
            //当前Obj是否属于Activity，并且不是当前 Activity
            if( val instanceof Activity ) ((Activity) val).finish();
            //当前Obj是否属于Service，并且不是当前 Service
            if( val instanceof Service ) ((Service) val).stopSelf();
        }
    }

    /**
     是否还有存在的 Activity or Service
     @param type            0：全部，1：Activity，2：Service
     @param isKeep          true：存在的，false：跳过的
     @param objArr          跳过的对象（支持Class和类名）
     @return                是否存在
     */
    private boolean haveExist(int type, boolean isKeep, Object... objArr) {
        if( mExitAppMap == null || mExitAppMap.size() == 0 ) return false;
        for( String key : mExitAppMap.keySet() ) {
            boolean isContinue = false;
            if( objArr != null && objArr.length > 0 ) {
                for( Object obj : objArr ) {
                    if( obj == null ) continue;
                    if( !key.equals( getObjName( obj ) ) ) continue;
                    if( !isKeep ) isContinue = true;
                    break;
                }
            }
            if( isContinue ) continue;
            Object obj = mExitAppMap.get( key );
            if( ( type == 0 || type == 1 ) && obj instanceof Activity ) return true;
            if( ( type == 0 || type == 2 ) && obj instanceof Service ) return true;
        }
        return false;
    }

    /**
     是否还有存在的 Activity
     @param keepObjArr      这几个 Activity
     @return                是否存在
     */
    public boolean isHaveExistActivity(Object... keepObjArr) {
        return haveExist( 1, true, keepObjArr );
    }

    /**
     是否还有存在的 Activity
     @param skipObjArr      除了这几个 Activity
     @return                是否存在
     */
    public boolean isHaveExistActivityOfSkip(Object... skipObjArr) {
        return haveExist( 1, false, skipObjArr );
    }

    /**
     是否还有存在的 Service
     @param keepObjArr      这几个 Service
     @return                是否存在
     */
    public boolean isHaveExistService(Object... keepObjArr) {
        return haveExist( 2, true, keepObjArr );
    }

    /**
     是否还有存在的 Service
     @param skipObjArr      除了这几个 Service
     @return                是否存在
     */
    public boolean isHaveExistServiceOfSkip(Object... skipObjArr) {
        return haveExist( 2, false, skipObjArr );
    }

    /**
     * 是否还有存在的 Activity or Service
     * @param keepObjArr    这几个 Activity or Service
     * @return              是否存在
     */
    public boolean isHaveExistAll(Object... keepObjArr) {
        return haveExist( 0, true, keepObjArr );
    }

    /**
     * 是否还有存在的 Activity or Service
     * @param skipObjArr    除了这几个 Activity or Service
     * @return              是否存在
     */
    public boolean isHaveExistAllOfSkip(Object... skipObjArr) {
        return haveExist( 0, false, skipObjArr );
    }

    /**
     重启app
     @param context                 上下文
     @param reStartActivityClass    重启时调用的 Activity（一般是 BaseActivity）
     */
    public void reStartApp(@NonNull Context context, @NonNull Class<?> reStartActivityClass) {
        Intent intent;
        //重启APP前发送通知
        DOM.getInstance().setResult( DOMConstant.DOM_RESTART_APP );

        intent = new Intent( context, reStartActivityClass );
        intent.setFlags( Intent.FLAG_ACTIVITY_NEW_TASK );
        context.startActivity( intent );
        //关闭当前进程
        Process.killProcess( Process.myPid() );
    }

    /**
     延迟重启app
     @param context       上下文
     @param reStartActivityClass    重启时调用的 Activity（一般是 BaseActivity）
     @param delayMillis   延迟毫秒
     */
    public <A extends Class<Activity>> void reStartApp(@NonNull Context context,
                                                       @NonNull Class<?> reStartActivityClass,
                                                       long delayMillis) {
        mHandler.post(() -> reStartApp( context, reStartActivityClass ), delayMillis);
    }

    /**
     * 双击退出app
     * @param context  上下文
     */
    public void doubleBackPressedExit(Context context,
                                      @NonNull String defHint,
                                      Build hintStyle,
                                      Function<Boolean, Boolean> call) {
        if( context == null ) return;
        long secondTime = System.currentTimeMillis();
        boolean ret = true;
        if( secondTime - firstTime > 2000 ) {
            firstTime = secondTime;
            if( call != null ) ret = call.apply( false );
            if( ret ) {
                defHint = mExitHint == null ? defHint : mExitHint;
                //弹出提示
                ToastManage.get().showToast(context, defHint, hintStyle );
                //双击返回第一次通知
                DOM.getInstance().setResult( DOMConstant.DOM_DOUBLE_BACK_EXIT_START );
            }
        } else {
            if( call != null ) ret = call.apply( true );
            if( ret ) {
                //双击返回最后一次通知
                DOM.getInstance().setResult( DOMConstant.DOM_DOUBLE_BACK_EXIT_END );
                exitApp( context );
            }
        }
    }

    public void doubleBackPressedExit(Context context,
                                      @NonNull String defHint,
                                      Function<Boolean, Boolean> call) {
        doubleBackPressedExit( context,
                defHint,
                ToastManage.get().getBuild(),
                call
        );
    }

    public void doubleBackPressedExit(Context context, @NonNull String defHint) {
        doubleBackPressedExit( context, defHint, null );
    }

    public void doubleBackPressedExit(Context context, Function<Boolean, Boolean> call) {
        doubleBackPressedExit( context, String.format(isZH() ?
                "再按一次退出%s" :
                "Click exit %s again", AppUtil.getAppName( context )
        ), call);
    }

    public void doubleBackPressedExit(Context context) {
        doubleBackPressedExit( context, ret -> true );
    }

    /**
     * 返回到桌面
     * @param context   上下文
     */
    public void backPressedHome(@NonNull Context context) {
        Intent intent;
        //回到桌面通知
        DOM.getInstance().setResult( DOMConstant.DOM_BACK_PRESSED_HOME );

        intent = new Intent( Intent.ACTION_MAIN );
        intent.addFlags( Intent.FLAG_ACTIVITY_CLEAR_TOP );
        intent.addCategory( Intent.CATEGORY_HOME );
        context.startActivity( intent );
    }

    /**
     是否切换到桌面
     @param context     上下文
     @return            是否切换到桌面
     */
    public boolean isGotoHome(@NonNull Context context) {
        ActivityManager am = (ActivityManager) context.getSystemService( Activity.ACTIVITY_SERVICE );
        List<ActivityManager.RunningTaskInfo> list = am.getRunningTasks( 1 );
        if( list == null || list.size() == 0 ) return false;
        ActivityManager.RunningTaskInfo info = list.get( 0 );
        if( info == null ) return false;
        ComponentName cn = info.topActivity;
        if( cn == null ) return false;
        return !cn.getPackageName().contains( AppUtil.getPackageName( context ) );
    }

    @Nullable
    private String getObjName(Object obj) {
        if( obj == null ) return null;
        if( obj instanceof String ) return obj.toString();
        return obj.getClass().getSimpleName();
    }
}
